package cn.itcast.hotel.service.impl;

import cn.itcast.hotel.mapper.HotelMapper;
import cn.itcast.hotel.pojo.Hotel;
import cn.itcast.hotel.pojo.HotelDoc;
import cn.itcast.hotel.pojo.PageResult;
import cn.itcast.hotel.pojo.RequestParams;
import cn.itcast.hotel.service.IHotelService;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {

    @Autowired
    private RestHighLevelClient client;

    @Override
    public PageResult search(RequestParams params) {
        // key 搜索框内容      page 分页属性
        // 1. 创建search请求
        SearchRequest request = new SearchRequest("hotel");
        // 构建所有的请求参数
        SearchSourceBuilder source = request.source();
        buildBasicQuery(params,source);
//        String key = params.getKey();
//        if (StringUtils.isNotBlank(key)) {  // isEmpty  isNotEmpty
//            source.query(QueryBuilders.matchQuery("all",key));
//        }else {
//            source.query(QueryBuilders.matchAllQuery());
//        }
        Integer page = params.getPage(); // 页码
        Integer size = params.getSize(); // 每页条数
        source.from((page - 1)*size);
        source.size(size);
        // 2. 调用client执行请求
        return handleResult(request);
    }

    /**
     * 用于封装查询条件
     * @param params
     * @param source
     */
    private void buildBasicQuery(RequestParams params, SearchSourceBuilder source) {
        // 1 构建bool条件
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 1.1 构建关键词key match搜索   must 算分
        String key = params.getKey();
        if (StringUtils.isNotBlank(key)) {  // isEmpty  isNotEmpty
            boolQueryBuilder.must(QueryBuilders.matchQuery("all",key));
        }else {
            boolQueryBuilder.must(QueryBuilders.matchAllQuery());
        }
        // 1.2 构建城市city term搜索    filter
        String city = params.getCity();
        if (StringUtils.isNotBlank(city)) {  // isEmpty  isNotEmpty
            boolQueryBuilder.filter(QueryBuilders.termQuery("city",city));
        }
        // 1.3 构建品牌 brand term搜索  filter
        String brand = params.getBrand();
        if (StringUtils.isNotBlank(brand)) {  // isEmpty  isNotEmpty
            boolQueryBuilder.filter(QueryBuilders.termQuery("brand",brand));
        }
        // 1.4 构建星级 starName  term搜索  filter
        String starName = params.getStarName();
        if (StringUtils.isNotBlank(starName)) {  // isEmpty  isNotEmpty
            boolQueryBuilder.filter(QueryBuilders.termQuery("starName",starName));
        }
        // 1.5 构建价格范围  range 搜索  filter
        Integer minPrice = params.getMinPrice();
        Integer maxPrice = params.getMaxPrice();
        if(minPrice!=null && maxPrice!=null){
            boolQueryBuilder.filter(
                    QueryBuilders.rangeQuery("price").gte(minPrice).lt(maxPrice)
            );
        }

        String location = params.getLocation();
        if (StringUtils.isNotBlank(location)) { // 判断前端是否传入 地理位置坐标
            // 按照距离排序
            source.sort(SortBuilders.geoDistanceSort("location",new GeoPoint(location))
                    .order(SortOrder.ASC) // 距离升序
                    .unit(DistanceUnit.KILOMETERS) // 距离单位 km
            );
        }
        // 新增 算分查询条件
        FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(
                boolQueryBuilder,  // 原来的条件
                new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                     new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                             QueryBuilders.termQuery("isAD",true), // 代表打广告的酒店
                             ScoreFunctionBuilders.weightFactorFunction(10)
                     )
                }
        ).scoreMode(FunctionScoreQuery.ScoreMode.MULTIPLY); // 设置加权方式:  相乘
        source.query(functionScoreQuery);
    }

    /**
     * 抽取 结果处理的代码
     * @param request
     * @return
     */
    private PageResult handleResult(SearchRequest request) {
        try {
            // 执行请求得到响应结果
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            // 3. 解析响应结果
            SearchHits searchHits = response.getHits();
            // 查询数量
            long total = searchHits.getTotalHits().value;
            List<HotelDoc> list = new ArrayList<>();
            // 按照kibana上的格式 去解析结果
            SearchHit[] hits = searchHits.getHits();
            for (SearchHit hit : hits) {
                // 得到文档内容json
                String docJson = hit.getSourceAsString();
                HotelDoc hotelDoc = JSON.parseObject(docJson, HotelDoc.class);

                Object[] sortValues = hit.getSortValues(); // 得到 sort 排序字段的值
                if(sortValues!=null && sortValues.length > 0){
                    hotelDoc.setDistance(sortValues[0]); // 设置距离
                }

                list.add(hotelDoc);
            }
            // 封装返回结果
            return new PageResult(total,list);
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException();
        }
    }
}
